package com.yunxin.download.server.service.impl;

import cn.afterturn.easypoi.word.WordExportUtil;
import cn.afterturn.easypoi.word.entity.MyXWPFDocument;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import com.yunxin.common.exception.BusinessException;
import com.yunxin.download.server.infra.constant.enums.FileTypeEnum;
import com.yunxin.download.server.service.extenal.DownloadFileDTO;
import com.yunxin.download.server.service.extenal.FileExportConfigDTO;
import com.yunxin.download.server.infra.constant.DownloadCommonConstant;
import com.yunxin.download.server.infra.constant.FileExportConstant;
import com.yunxin.download.server.infra.constant.enums.DownloadResultCode;
import com.yunxin.download.server.infra.repository.DownloadDao;
import com.yunxin.download.server.infra.support.datasource.annotation.DataSourceDynamicSwitch;
import com.yunxin.download.server.infra.support.file.export.context.FileExportContext;
import com.yunxin.download.server.infra.support.file.export.context.FileExportContextFactory;
import com.yunxin.download.server.infra.support.filed.transfer.FieldTransferContext;
import com.yunxin.download.server.infra.utils.AliYunOssUtil;
import com.yunxin.download.server.service.DownloadService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.io.*;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;

/**
 * @author zero°
 * @since 2023-06-15
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class DownloadServiceImpl extends GeneratorExportService implements DownloadService {

    private static final String EXPORT_REPLACE_FLAG = "##";

    private static final String EXPORT_FLAG = "{." + EXPORT_REPLACE_FLAG + "}";

    private static final String ID_COL = "id";

    private final DownloadDao downloadDao;

    @Override
    @DataSourceDynamicSwitch
    public void downloadFile(DownloadFileDTO downloadFileDTO) {
        String fileName = getFileName(downloadFileDTO.getMethodNme());
        List<FileExportConfigDTO> fileExportConfigs = downloadFileDTO.getExportConfig();
        // 是否包含子集
        final Boolean isContainCollection = isContainCollection(fileExportConfigs);
        // 是否应该用单独的策略模式实现?
        if (Boolean.TRUE.equals(isContainCollection)) {
            exportComplexCol(downloadFileDTO, fileExportConfigs, fileName);
        } else {
            exportSimple(downloadFileDTO, fileExportConfigs, fileName);
        }

    }

    @Override
    @DataSourceDynamicSwitch
    public void downloadWorldFile(DownloadFileDTO downloadFileDTO) {
        String fileName = getFileName(downloadFileDTO.getMethodNme());
        exportWorldSimple(downloadFileDTO, fileName);
    }

    /**
     * 是否包含子集
     *
     * @param excelExportConfigs excelExportConfigs
     * @return Boolean
     */
    private Boolean isContainCollection(List<FileExportConfigDTO> excelExportConfigs) {
        for (FileExportConfigDTO excelExportConfig : excelExportConfigs) {
            final List<FileExportConfigDTO> collections = excelExportConfig.getCollections();
            if (collections != null && !collections.isEmpty()) {
                return true;
            }
        }
        return false;
    }

    /**
     * 集合导出
     *
     * @param downloadFileDTO    downloadFileDTO
     * @param excelExportConfigs excelExportConfigs
     * @param fileName           fileName
     */
    private void exportComplexCol(DownloadFileDTO downloadFileDTO, List<FileExportConfigDTO> excelExportConfigs, String fileName) {
        Map<String, FileExportConfigDTO> exportConfigMap = new HashMap<>(2);
        Map<String, Set<String>> collecitonMap = new HashMap<>();
        List<String> collectionFieldNames = new ArrayList<>();
        //子列字段名集合
        Set<String> collectionEleSet = new HashSet<>();
        for (final FileExportConfigDTO excelExportConfigDTO : excelExportConfigs) {
            //获取子列的相关属性
            final List<FileExportConfigDTO> collections = excelExportConfigDTO.getCollections();
            if (collections != null && !collections.isEmpty()) {
                final String distinctFieldName = excelExportConfigDTO.getDistinctFieldName();
                if (CharSequenceUtil.isBlank(distinctFieldName)) {
                    throw new BusinessException(DownloadResultCode.COLLECTION_MUST_DISTINCT_FILE_NAME);
                }
                collecitonMap.put(distinctFieldName, collections.stream().map(FileExportConfigDTO::getFieldName).collect(Collectors.toSet()));
                for (final FileExportConfigDTO exportConfigCollection : collections) {
                    collectionEleSet.add(exportConfigCollection.getFieldName());
                    collectionFieldNames.add(exportConfigCollection.getFieldName());
                    exportConfigMap.put(exportConfigCollection.getFieldName(), exportConfigCollection);
                }
            } else {
                exportConfigMap.put(excelExportConfigDTO.getFieldName(), excelExportConfigDTO);
            }
        }
        exportCommon(downloadFileDTO, fileName, dataList -> {
            Map<String, List<Map<String, Object>>> collectionDataMap = new HashMap<>(16);
            Set<Long> idSet = new HashSet<>();
            Map<Long, Set<Object>> distinctFieldValueSet = new HashMap<>();
            List<Map<String, Object>> singleIdDataList = new ArrayList<>();
            //组装数据  类似于mybatis的resultType里面的collection
            for (int i = 0; i < dataList.size(); i++) {
                final Map<String, Object> data = dataList.get(i);
                final Object idCol = data.get(ID_COL);
                //不判空了 如果没有id  说明数据是乱的
                Long id = Long.parseLong(idCol.toString());
                final Set<Map.Entry<String, Set<String>>> entries = collecitonMap.entrySet();
                for (Map.Entry<String, Set<String>> entry : entries) {
                    final String distinctFieldName = entry.getKey();
                    final Set<String> collectionFieldNameSets = entry.getValue();
                    final Object distinctFieldValue = data.get(distinctFieldName);
                    if (distinctFieldValue == null) {
                        continue;
                    }
                    Set<Object> objectSet = distinctFieldValueSet.computeIfAbsent(id, k -> new HashSet<>());
                    if (objectSet.add(distinctFieldValue)) {
                        List<Map<String, Object>> collectionDataList = collectionDataMap.computeIfAbsent(id + distinctFieldName, k -> new ArrayList<>());
                        Map<String, Object> collectionData = new HashMap<>(collectionFieldNameSets.size());
                        for (String collectionFieldName : collectionFieldNameSets) {
                            collectionData.put(collectionFieldName, data.get(collectionFieldName));
                        }
                        collectionDataList.add(collectionData);
                    }
                }
                if (idSet.add(id)) {
                    //只添加id不重复的Key  清楚collectionsd的值
                    singleIdDataList.add(data);
                    for (int i1 = 0; i1 < collectionFieldNames.size(); i1++) {
                        final String collectionFieldName = collectionFieldNames.get(i1);
                        data.put(collectionFieldName, null);
                    }
                }
            }
            //合并数据
            List<Map<String, Object>> newDataList = new ArrayList<>();
            for (final Map<String, Object> data : singleIdDataList) {
                //获取collection
                final Object idCol = data.get(ID_COL);
                //不判空了 如果没有id  说明数据是乱的
                Long id = Long.parseLong(idCol.toString());
                final Set<Map.Entry<String, Set<String>>> entries = collecitonMap.entrySet();
                //id相同区间的数据集合
                List<Map<String, Object>> collectionAreaData = new ArrayList<>();
                collectionAreaData.add(data);
                for (Map.Entry<String, Set<String>> entry : entries) {
                    final String distinctFieldName = entry.getKey();
                    final List<Map<String, Object>> collectionDatas = collectionDataMap.get(id + distinctFieldName);
                    if (collectionDatas != null && !collectionDatas.isEmpty()) {
                        for (int i1 = 0; i1 < collectionDatas.size(); i1++) {
                            final Map<String, Object> collectionData = collectionDatas.get(i1);
                            if (i1 < collectionAreaData.size()) {
                                final Map<String, Object> indexedData = collectionAreaData.get(i1);
                                indexedData.putAll(collectionData);
                            } else {
                                collectionAreaData.add(collectionData);
                            }
                        }
                    }
                }
                newDataList.addAll(collectionAreaData);
            }
            handleData(newDataList, exportConfigMap);
            return newDataList;
        });

    }

    /**
     * 导出简单的
     *
     * @param downloadFileDTO   downloadFileDTO
     * @param fileExportConfigs fileExportConfigs
     * @param fileName          fileName
     */
    private void exportSimple(DownloadFileDTO downloadFileDTO, List<FileExportConfigDTO> fileExportConfigs, String fileName) {
        Map<String, FileExportConfigDTO> exportConfigMap = fileExportConfigs.stream().collect(Collectors.toMap(FileExportConfigDTO::getFieldName, t -> t));
        //滚动分页
        if (downloadFileDTO.getLimitType() == 0) {
            exportCommon(downloadFileDTO, fileName, dataList -> {
                handleData(dataList, exportConfigMap);
                return dataList;
            });
            //下标分页
        } else {
            exportPageLimitCommon(downloadFileDTO, fileName, dataList -> {
                handleData(dataList, exportConfigMap);
                return dataList;
            });
        }
    }

    /**
     * 导出world简单的
     *
     * @param downloadFileDTO downloadFileDTO
     * @param fileName        fileName
     */
    private void exportWorldSimple(DownloadFileDTO downloadFileDTO, String fileName) {
        //滚动分页
        exportWorldCommon(downloadFileDTO, fileName);
//        if (downloadFileDTO.getLimitType() == 0) {
//
//            //下标分页
//        } else {
//            exportPageLimitCommon(downloadFileDTO, fileName);
//        }
    }


    /**
     * 滚动分页导出
     *
     * @param downloadFileDTO downloadFileDTO
     * @param fileName        fileName
     * @param dataHandle      dataHandle
     */
    private void exportCommon(DownloadFileDTO downloadFileDTO, String fileName, UnaryOperator<List<Map<String, Object>>> dataHandle) {
        String sql = DownloadCommonConstant.SQL_SCRIPT_START + downloadFileDTO.getSql() + " limit " + downloadFileDTO.getLimit() + DownloadCommonConstant.SQL_SCRIPT_END;
        export(downloadFileDTO.getDownloaderName(), downloadFileDTO.getMethodConfigId(), fileName, () -> {
            try (InputStream inputStream = AliYunOssUtil.X.download(FileExportConstant.OSS_DOWNLOAD_FILE_TEMPLATE_DIR + downloadFileDTO.getTemplateUrl())) {
                FileExportContext fileExportContext = FileExportContextFactory.newInputSteamTemplateContext(downloadFileDTO.getTemplateUrl(), inputStream);
                long lastBatchMaxId = 0L;
                int rows = 0;
                for (; ; ) {
                    //拼接参数
                    Map<String, Object> params = downloadFileDTO.getParams();
                    params.put(DownloadCommonConstant.SQL_LAST_BATCH_MAX_ID_PARAM, lastBatchMaxId);
                    // 拼接标签让mybatis解析
                    params.put(DownloadCommonConstant.SQL_CONTENT, sql);
                    List<Map<String, Object>> result = downloadDao.download(params);
                    if (downloadFileDTO.getMethodCode().equals("weChatUser")) {
                        for (Map<String, Object> stringObjectMap : result) {
                            Long customerId = (Long) stringObjectMap.get("id");
                            LocalDateTime o = (LocalDateTime) stringObjectMap.get("lastFollowTime");
                            if (ObjectUtil.isNotEmpty(o)) {
                                Instant instant = o.atZone(ZoneId.systemDefault()).toInstant();
                                Date date = Date.from(instant);
                                List<String> tagName = downloadDao.allTagList(customerId);
                                String name = String.join(",", tagName);
                                stringObjectMap.put("tag", "未知");
                                if (CollUtil.isNotEmpty(tagName)) {
                                    stringObjectMap.put("tag", name);
                                }

                                if (ObjectUtil.isNotEmpty(date)) {
                                    // 计算时间间隔 绝对值
                                    Date currentDate = new Date();
                                    long timeDifferenceInMillis = Math.abs(currentDate.getTime() - date.getTime());
                                    // 将毫秒转换为小时
                                    long hours = timeDifferenceInMillis / (1000 * 60 * 60);
                                    if (hours <= 24 && hours > 0) {
                                        stringObjectMap.put("lastFollowTime", hours + "小时");
                                    } else if (hours > 0) {
                                        long day = hours / 24; // 商
                                        long hour = hours % 24; // 取余数
                                        stringObjectMap.put("lastFollowTime", day + "天" + "零" + hour + "小时");
                                    }
                                }
                            }
                        }
                    }

                    rows += result.size();
                    if (result.isEmpty()) {
                        break;
                    }
                    lastBatchMaxId = result.stream().map(x -> Long.parseLong(x.get(ID_COL).toString())).max(Long::compareTo).orElse(Long.MAX_VALUE);

                    //数据处理
                    final List<Map<String, Object>> datas = dataHandle.apply(result);
                    fileExportContext.appendDataByMap(datas, downloadFileDTO.getIsView());
                }

                //没有数据也进行渲染  防止出现占位符
                if (rows == 0) {
                    fileExportContext.appendDataByMap(CollUtil.newArrayList(new HashMap<>(2)), downloadFileDTO.getIsView());
                }
                return fileExportContext.export2Oss(fileExportContext.getFileTypeSuffix(), downloadFileDTO.getDownloaderName(), fileName);
            } catch (Exception e) {
                log.error("导出失败,methodCode={}, methodConfigId={}, userName={}, e", downloadFileDTO.getMethodCode(), downloadFileDTO.getMethodConfigId(), downloadFileDTO.getDownloaderName(), e);
                return "";
            }
        });

    }

    /**
     * 滚动分页导出
     *
     * @param downloadFileDTO downloadFileDTO
     * @param fileName        fileName
     */
    private void exportWorldCommon(DownloadFileDTO downloadFileDTO, String fileName) {
        String sql = DownloadCommonConstant.SQL_SCRIPT_START + downloadFileDTO.getSql() + " limit " + downloadFileDTO.getLimit() + DownloadCommonConstant.SQL_SCRIPT_END;
        export(downloadFileDTO.getDownloaderName(), downloadFileDTO.getMethodConfigId(), fileName, () -> {
            try {
                //InputStream inputStream = AliYunOssUtil.X.download(FileExportConstant.OSS_DOWNLOAD_FILE_TEMPLATE_DIR + downloadFileDTO.getTemplateUrl());
                //MyXWPFDocument worldDoc = new MyXWPFDocument(inputStream);
                //获得填充数据
                Map<String, Object> params = downloadFileDTO.getParams();
                Map<String, Object> data = (Map) params.get("data");
                long lastBatchMaxId = 0L;
                int rows = 0;
                List<Map<String, Object>> dataList = new ArrayList<>();
                for (; ; ) {
                    //拼接参数
                    Map<String, Object> conditions = (Map) params.get("conditions");
                    conditions.put(DownloadCommonConstant.SQL_LAST_BATCH_MAX_ID_PARAM, lastBatchMaxId);
                    // 拼接标签让mybatis解析
                    conditions.put(DownloadCommonConstant.SQL_CONTENT, sql);
                    List<Map<String, Object>> result = downloadDao.download(conditions);
                    if (downloadFileDTO.getMethodCode().equals("productAdvanceCharge")) {
                        if (!ObjectUtils.isEmpty(result) && ObjectUtils.isEmpty(data.get("merchantName"))) {
                            Map<String, Object> stringObjectMap = result.get(0);
                            data.put("merchantName", stringObjectMap.get("merchantName"));
                        }
                        if (!ObjectUtils.isEmpty(result) && ObjectUtils.isEmpty(data.get("productName"))) {
                            Map<String, Object> stringObjectMap = result.get(0);
                            data.put("productName", stringObjectMap.get("productName"));
                        }
                    } else if (downloadFileDTO.getMethodCode().equals("channelAdvanceChargeInvoice")) {
                        if (!ObjectUtils.isEmpty(result) && ObjectUtils.isEmpty(data.get("channelName"))) {
                            Map<String, Object> stringObjectMap = result.get(0);
                            data.put("channelName", stringObjectMap.get("channelName"));
                        }
                    }
                    //log.error("查询数据", JSONUtils.toJSONString(result));
                    rows += result.size();
                    if (result.isEmpty()) {
                        break;
                    }
                    lastBatchMaxId = result.stream().map(x -> Long.parseLong(x.get(ID_COL).toString())).max(Long::compareTo).orElse(Long.MAX_VALUE);
                    //数据处理
                    dataList.addAll(result);
                }
                data.put("list", dataList);

                XWPFDocument worldDoc = WordExportUtil.exportWord07(downloadFileDTO.getTemplateUrl(), data);
                //WordExportUtil.exportWord07(worldDoc, params);
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                //worldDoc.write(byteArrayOutputStream);
                // 把文件写入字节输出流
                worldDoc.write(byteArrayOutputStream);
                // 输出流转成输入流
                InputStream inputStreams = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
                //生成随机osskey
                String ossKey = FileExportConstant.OSS_FILE_FOLDER + downloadFileDTO.getDownloaderName() + "/" + downloadFileDTO.getDownloaderName() + "-" + fileName + FileTypeEnum.DOCX.getType();
                AliYunOssUtil.X.upload(ossKey, inputStreams);
                return ossKey;
            } catch (Exception e) {
                log.error("导出失败,methodCode={}, methodConfigId={}, userName={}, e", downloadFileDTO.getMethodCode(), downloadFileDTO.getMethodConfigId(), downloadFileDTO.getDownloaderName(), e);
                return "";
            }
        });

    }

    /**
     * 深度分页导出
     *
     * @param downloadFileDTO downloadFileDTO
     * @param fileName        fileName
     * @param dataHandle      dataHandle
     */
    private void exportPageLimitCommon(DownloadFileDTO downloadFileDTO, String fileName, UnaryOperator<List<Map<String, Object>>> dataHandle) {
        String sql = DownloadCommonConstant.SQL_SCRIPT_START + downloadFileDTO.getSql() + " limit #{pageIndex}, " + downloadFileDTO.getLimit() + DownloadCommonConstant.SQL_SCRIPT_END;
        export(downloadFileDTO.getDownloaderName(), downloadFileDTO.getMethodConfigId(), fileName, () -> {
            try (InputStream inputStream = AliYunOssUtil.X.download(FileExportConstant.OSS_DOWNLOAD_FILE_TEMPLATE_DIR + downloadFileDTO.getTemplateUrl())) {
                FileExportContext fileExportContext = FileExportContextFactory.newInputSteamTemplateContext(downloadFileDTO.getTemplateUrl(), inputStream);
                int rows = 0;
                int pageNum = 1;
                for (; ; ) {
                    //拼接参数
                    Map<String, Object> params = downloadFileDTO.getParams();
                    params.put(DownloadCommonConstant.SQL_PAGE_INDEX, (pageNum - 1) * downloadFileDTO.getLimit());
                    //拼接标签让mybatis解析
                    params.put(DownloadCommonConstant.SQL_CONTENT, sql);
                    List<Map<String, Object>> result = downloadDao.download(params);
                    rows += result.size();
                    if (result.isEmpty()) {
                        break;
                    }
                    pageNum++;
                    //数据处理
                    final List<Map<String, Object>> dataList = dataHandle.apply(result);
                    fileExportContext.appendDataByMap(dataList, downloadFileDTO.getIsView());
                }
                //没有数据也进行渲染  防止出现占位符
                if (rows == 0) {
                    fileExportContext.appendDataByMap(CollUtil.newArrayList(new HashMap<>()), downloadFileDTO.getIsView());
                }
                return fileExportContext.export2Oss(fileExportContext.getFileTypeSuffix(), downloadFileDTO.getDownloaderName(), fileName);
            } catch (Exception e) {
                log.error("导出失败,methodCode={}, methodConfigId={}, userName={}, e", downloadFileDTO.getMethodCode(), downloadFileDTO.getMethodConfigId(), downloadFileDTO.getDownloaderName(), e);
                return "";
            }
        });
    }

    /**
     * 处理数据
     *
     * @param list            list
     * @param exportConfigMap exportConfigMap
     */
    private void handleData(List<Map<String, Object>> list, Map<String, FileExportConfigDTO> exportConfigMap) {
        //类型转换 jdbc类型转为java类型（现在全转为string）
        for (Map<String, Object> datum : list) {
            //获取每一行的数据
            datum.forEach((key, value) -> {
                if (value != null) {
                    FileExportConfigDTO fileExportHeaderConfigDTO = exportConfigMap.get(key);
                    if (fileExportHeaderConfigDTO != null) {
                        formatFiled(fileExportHeaderConfigDTO, datum, key);
                    }
                }
            });
        }
    }

    /**
     * 字段格式化
     *
     * @param fileExportHeaderConfigDTO fileExportHeaderConfigDTO
     * @param datum                     datum
     * @param key                       key
     */
    private void formatFiled(FileExportConfigDTO fileExportHeaderConfigDTO, Map<String, Object> datum, String key) {
        List<FileExportConfigDTO.FieldFormat> fieldFormats = fileExportHeaderConfigDTO.getFieldFormats();
        String fieldType = fileExportHeaderConfigDTO.getFieldType();
        //数据格式化处理
        if (CharSequenceUtil.isNotBlank(fieldType)) {
            Object transferValue = null;
            if (fieldFormats != null && !fieldFormats.isEmpty()) {
                transferValue = datum.get(key);
                for (FileExportConfigDTO.FieldFormat fieldFormat : fieldFormats) {
                    transferValue = FieldTransferContext.X.transfer(transferValue, fieldFormat);
                }
            } else {
                //无格式化默认使用filedType做类型转换
                FileExportConfigDTO.FieldFormat fieldFormat = new FileExportConfigDTO.FieldFormat();
                fieldFormat.setType(fieldType);
                transferValue = FieldTransferContext.X.transfer(datum.get(key), fieldFormat);
            }
            datum.put(key, transferValue == null ? "" : transferValue);
        }
    }


}
